package gateway

import (
	"APT/internal/consts"
	"APT/internal/dao"
	"APT/internal/global"
	"APT/internal/library/contexts"
	"APT/internal/library/location"
	"APT/internal/library/token"
	_ "APT/internal/logic"
	"APT/internal/middleware"
	"APT/internal/model/entity"
	"APT/internal/model/model_gateway"
	"APT/internal/service"
	"bytes"
	"context"
	"encoding/hex"
	"encoding/json"
	"errors"
	_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
	_ "github.com/gogf/gf/contrib/nosql/redis/v2"
	"github.com/gogf/gf/v2/container/gvar"
	"github.com/gogf/gf/v2/crypto/gaes"
	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/errors/gcode"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/gf/v2/os/gtime"
	"github.com/gogf/gf/v2/util/gconv"
	"io"
	"net/http"
	"strings"
)

var (
	AesKey = []byte("202CB962AC59075B964B07152D234B70")
	AesIv  = []byte("233FA6B19FDE8CAF")
	//SignKey = "D9840773233FA6B19FDE8CAF765402F5"
)

// AppGateway 总入口
func AppGateway() {
	var (
		ctx = context.Background()
	)
	global.Init(ctx)
	tk, err := service.BasicsConfig().GetLoadToken(ctx)
	if err != nil {
		return
	}
	token.SetConfig(tk)
	s := g.Server()
	g.Log().Path("logs/APP_GATEWAY").Info(context.TODO(), "start logs")
	// 注册全局中间件
	s.BindMiddleware("/*any", []ghttp.HandlerFunc{
		middleware.Ctx,       // 文件日志记录
		middleware.ServerLog, // 日志记录
		Response,             // 网关响应
		middleware.CORS,      // 跨域中间件，自动处理跨域问题
		middleware.Blacklist, // IP黑名单中间件，如果请求IP被后台拉黑，所有请求将被拒绝
		middleware.ApiIsAuth, // API鉴权
	}...)
	s.BindHandler("GET:/*", gatewayHandler)
	s.BindHandler("POST:/*", gatewayHandler)
	s.BindHandler("OPTIONS:/*", gatewayHandler)
	s.BindHandler("PUT:/*", gatewayHandler)
	s.BindHandler("DELETE:/*", gatewayHandler)
	s.BindHandler("PATCH:/*", gatewayHandler)
	s.SetPort(8008)
	s.Run()
}

// 网关处理函数
func gatewayHandler(greq *ghttp.Request) {
	var (
		targetPmsURL  = "http://127.0.0.1:8000"
		targetTpURL   = "http://127.0.0.1:9000"
		decryptedBody []byte
		encryptedBody []byte
		err           error
		req           *http.Request
		resp          *http.Response
		client        = &http.Client{}
		params        map[string]*g.Var
		body          []byte
		buffParams    *bytes.Buffer
		jumpUrl       = []string{
			"/api/upload/file",
		}
		ctx = greq.Context()
	)
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, "--[REQUEST]----------------------------------------------------------------")
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, greq.Request.URL.Path)
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, greq.Request.Method)
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, greq.GetBodyString())
	if g.IsEmpty(greq.GetBodyString()) {
		buffParams = bytes.NewBuffer(greq.GetBody())
	} else if !containsString(jumpUrl, greq.URL.Path) {
		if encryptedBody, err = io.ReadAll(greq.Body); err != nil {
			err = errors.New("parsing fail")
			goto ERR
		}
		if decryptedBody, err = Decrypt(string(encryptedBody)); err != nil {
			err = errors.New("parsing fail")
			goto ERR
		}
		g.Log().Path("logs/APP_GATEWAY").Info(ctx, string(decryptedBody))
		// 重新设置请求体
		greq.Body = io.NopCloser(bytes.NewReader(decryptedBody))
		// 调用 ReloadParam 以确保请求体的参数被重新加载
		greq.ReloadParam()

		if err = json.Unmarshal(decryptedBody, &params); err != nil {
			err = errors.New("parsing fail")
			goto ERR
		}
		if g.IsEmpty(params["sign"]) {
			err = errors.New("sign is empty")
			return
		}
		contexts.SetData(ctx, "request.body", decryptedBody)
		buffParams = bytes.NewBuffer(decryptedBody)
	} else {
		buffParams = bytes.NewBuffer(greq.GetBody())
	}
	if strings.HasPrefix(greq.URL.Path, "/api") {
		// 创建新的请求
		if req, err = http.NewRequest(greq.Method, targetPmsURL+greq.URL.Path, buffParams); err != nil {
			goto ERR
		}
		// 复制请求头
		req.Header = greq.Header
		req.Header.Set("X-Forwarded-For", greq.GetClientIp())
	} else {
		// 创建新的请求
		if req, err = http.NewRequest(greq.Method, targetTpURL+greq.URL.Path, buffParams); err != nil {
			goto ERR
		}
		// 复制请求头
		g.Log().Info(ctx, contexts.GetMemberUser(ctx))
		req.Header = greq.Header
		req.Header.Set("MemberInfo", gjson.New(contexts.GetMemberUser(ctx)).String())
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-Forwarded-For", greq.GetClientIp())
	}
	if !g.IsEmpty(req.Header.Get("Accept-Encoding")) {
		req.Header.Set("Accept-Encoding", "identity")
	}
	req.Header.Set("x-language", contexts.GetLanguage(ctx))
	for k, v := range req.Header {
		g.Log().Path("logs/APP_GATEWAY").Info(ctx, " -H ", k, v[0])
	}
	// 复制请求头
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, greq.URL.Path, greq.Method, greq.Header, params)
	// 发送请求到目标服务器
	if resp, err = client.Do(req); err != nil {
		goto ERR
	}
	defer func() { _ = resp.Body.Close() }()
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, "--[RESPONSE]----------------------------------------------------------------")
	// 读取目标服务器响应
	if body, err = io.ReadAll(resp.Body); err != nil {
		goto ERR
	}
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, resp.StatusCode)
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, resp.Header)
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, string(body))
	if resp.StatusCode != 200 {
		err = gerror.New("Internal error")
		goto ERR
	}

	greq.Response.Header().Set("Content-Type", "application/json")
	greq.Response.Write(body)
	return
ERR:
	greq.SetError(err)
	return
}

// Response 网关响应中间件统一处理
func Response(r *ghttp.Request) {
	r.Middleware.Next()
	var (
		ghttpcode     model_gateway.GatewayCode
		err           error
		encryptedBody []byte
		ctx           = r.Context()
		body          []byte
		resJsonData   interface{}
	)
	if r.GetError() != nil {
		err = r.GetError()
		goto ERR
	}
	body = r.Response.Buffer()
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, body)
	// 加密响应
	if encryptedBody, err = Encrypt(body); err != nil {
		goto ERR
	}
	ghttpcode.ResCode = gcode.CodeOK.Code()
	ghttpcode.ResMessage = gcode.CodeOK.Message()
	ghttpcode.ResData = string(encryptedBody)
	g.Log().Info(ctx, body)
	if !g.IsEmpty(body) {
		if err = json.Unmarshal(body, &resJsonData); err != nil {
			goto ERR
		}
	}
	ghttpcode.ResJsonData = resJsonData
	contexts.SetGatewayCode(ctx, &ghttpcode)
	contexts.SetDataMap(r.Context(), g.Map{
		"request.takeUpTime": gtime.Now().Sub(gtime.New(r.EnterTime)).Milliseconds(),
		// ...
	})
	// 记录日志
	AnalysisLog(r.Context())
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, ghttpcode)
	r.Response.ClearBuffer()
	r.Response.Header().Set("Content-Type", "application/json")
	r.Response.WriteJsonExit(ghttpcode)
	return
ERR:
	g.Log().Path("logs/APP_GATEWAY").Error(ctx, err)
	ghttpcode.ResCode = gcode.CodeBusinessValidationFailed.Code()
	ghttpcode.ResMessage = err.Error()
	g.Log().Path("logs/APP_GATEWAY").Info(ctx, ghttpcode)
	r.Response.ClearBuffer()
	r.Response.Header().Set("Content-Type", "application/json")
	r.Response.WriteJsonExit(ghttpcode)
}

// HexDecode 16进制解码
func HexDecode(s string) ([]byte, error) {
	dst := make([]byte, hex.DecodedLen(len(s))) //申请一个切片, 指明大小. 必须使用hex.DecodedLen
	n, err := hex.Decode(dst, []byte(s))        //进制转换, src->dst
	if err != nil {
		g.Dump(err)
		err = errors.New("解析失败")
		return nil, err
	}
	return dst[:n], err //返回0:n的数据.
}

// HexEncode 字符串转为16进制
func HexEncode(s string) []byte {
	dst := make([]byte, hex.EncodedLen(len(s))) //申请一个切片, 指明大小. 必须使用hex.EncodedLen
	n := hex.Encode(dst, []byte(s))             //字节流转化成16进制
	return dst[:n]
}

// Decrypt 解密
func Decrypt(cipherText string) (bodyText []byte, err error) {
	var (
		BodyHex []byte
	)
	// 解密
	if BodyHex, err = HexDecode(cipherText); err != nil {
		return
	}
	if bodyText, err = gaes.DecryptCBC(BodyHex, AesKey, AesIv); err != nil {
		return
	}
	return
}

// Encrypt 加密
func Encrypt(plainText []byte) (bodyText []byte, err error) {
	var (
		BodyHex []byte
	)
	if BodyHex, err = gaes.EncryptCBC(plainText, AesKey, AesIv); err != nil {
		return
	}
	bodyText = HexEncode(string(BodyHex))
	return
}

// 判断字符串是否在slice中
func containsString(slice []string, str string) bool {
	for _, v := range slice {
		if v == str {
			return true
		}
	}
	return false
}

func AnalysisLog(ctx context.Context) {
	var (
		greq            = ghttp.RequestFromCtx(ctx)
		CtxId           = gctx.CtxId(ctx)
		mctx            = contexts.Get(ctx)
		response        = mctx.Response
		gatewayResponse = mctx.GatewayCode
		user            = mctx.User
		MemberUser      = mctx.MemberUser
		request         = ghttp.RequestFromCtx(ctx)
		module          = mctx.Module
		clientIp        = location.GetClientIp(request)
		postData        = gjson.New(consts.NilJsonToString)
		getData         = gjson.New(request.URL.Query())
		headerData      = gjson.New(consts.NilJsonToString)
		errorData       = gjson.New(consts.NilJsonToString)
		data            entity.SysLog
		memberId        int64
		errorCode       int
		errorMsg        string
		traceID         string
		timestamp       int64
		appId           string
		takeUpTime      int64
	)

	// 响应数据
	if response != nil {
		errorCode = response.Code
		errorMsg = response.Message
		traceID = CtxId
		timestamp = response.Timestamp
		if len(gconv.String(response.Error)) > 0 {
			errorData = gjson.New(response.Error)
		}
	}
	if gatewayResponse != nil {
		errorCode = gatewayResponse.ResCode
		errorMsg = gatewayResponse.ResMessage
		traceID = CtxId
		timestamp = gtime.Now().Timestamp()
		if !g.IsEmpty(greq.GetError()) {
			errorData = gjson.New(greq.GetError())
		}
	}

	if timestamp == 0 {
		timestamp = gtime.Timestamp()
	}

	// 请求头
	if reqHeadersBytes, _ := gjson.New(request.Header).MarshalJSON(); len(reqHeadersBytes) > 0 {
		headerData = gjson.New(reqHeadersBytes)
	}

	// post参数
	if body, ok := mctx.Data["request.body"].(*gjson.Json); ok {
		postData = body
	}

	// post表单
	postForm := gjson.New(gconv.String(request.PostForm)).Map()
	if len(postForm) > 0 {
		for k, v := range postForm {
			postData.MustSet(k, v)
		}
	}

	if postData.IsNil() || len(postData.Map()) == 0 {
		postData = gjson.New(consts.NilJsonToString)
	}

	// 当前登录用户
	if user != nil {
		memberId = user.Id
		appId = user.App
	}

	// 当前App登录用户
	if MemberUser != nil {
		memberId = gvar.New(MemberUser.MemberId).Int64()
		appId = MemberUser.App
	}

	ipData, err := location.GetLocation(ctx, clientIp)
	if err != nil {
		g.Log().Debugf(ctx, "location.GetLocation clientIp:%v, err:%+v", clientIp, err)
	}

	if ipData == nil {
		ipData = new(location.IpLocationData)
	}

	// 请求耗时
	if tt, ok := mctx.Data["request.takeUpTime"].(int64); ok {
		takeUpTime = tt
	}

	headerData.MustAppend("Enter-Time", request.EnterTime.String())

	data = entity.SysLog{
		AppId:       appId,
		AppMemberId: 0,
		MemberId:    memberId,
		Method:      request.Method,
		Module:      module,
		Url:         request.URL.Path,
		GetData:     getData,
		PostData:    postData,
		HeaderData:  SimplifyHeaderParams(headerData),
		Ip:          clientIp,
		ProvinceId:  ipData.ProvinceCode,
		CityId:      ipData.CityCode,
		ErrorCode:   errorCode,
		ErrorMsg:    errorMsg,
		ErrorData:   errorData,
		ReqId:       traceID,
		Timestamp:   timestamp,
		UserAgent:   request.Header.Get("User-Agent"),
		Status:      consts.StatusEnabled,
		TakeUpTime:  takeUpTime,
		UpdatedAt:   gtime.Now(),
		CreatedAt:   request.EnterTime,
	}
	_, logInsertErr := dao.SysLog.Ctx(ctx).Data(data).Insert()
	if logInsertErr != nil {
		g.Log().Error(ctx, logInsertErr)
	}
	return
}

func SimplifyHeaderParams(data *gjson.Json) *gjson.Json {
	if data == nil || data.IsNil() {
		return data
	}
	var filters = []string{"Accept", "Cookie"}
	for _, filter := range filters {
		v := data.Get(filter)
		if len(v.String()) > 128 {
			data.MustRemove(filter)
		}
	}
	return data
}
